#include <desc.h>
#include <vsprintf.h>
#include <string.h>
#include <page.h>

/* SekiOS - desc.c
 *
 * descriptor table things
 *
 * credits: Bran's Kernel Dev
 */

// Initialise our task state segment structure.
static void write_tss(int num, unsigned short ss0, unsigned int esp0)
{
   // Firstly, let's compute the base and limit of our entry into the GDT.
   unsigned int base = (unsigned int) &tss_entry;
   unsigned int limit = base + sizeof(tss_entry);

   // Now, add our TSS descriptor's address to the GDT.
   gdt_set_gate(num, base, limit, 0xE9, 0x00);

   // Ensure the descriptor is initially zero.
   memset(&tss_entry, 0, sizeof(tss_entry));

   tss_entry.ss0  = ss0;  // Set the kernel stack segment.
   tss_entry.esp0 = esp0; // Set the kernel stack pointer.

   // setup TSS
   // this lets anyone switch to kernel mode

   tss_entry.cs = 0x08 | 0x03; // RPL
   tss_entry.ss = tss_entry.ds = tss_entry.es = 
   	tss_entry.fs = tss_entry.gs = 0x10 | 0x03;
} 

void tss_set_stack(unsigned int stack)
{
   tss_entry.esp0 = stack;
} 

/* Setup a descriptor in the Global Descriptor Table */
void
gdt_set_gate (int num, unsigned long base, unsigned long limit,
	      unsigned char access, unsigned char gran)
{
  /* Setup the descriptor base address */
  gdt[num].base_low = (base & 0xFFFF);
  gdt[num].base_middle = (base >> 16) & 0xFF;
  gdt[num].base_high = (base >> 24) & 0xFF;

  /* Setup the descriptor limits */
  gdt[num].limit_low = (limit & 0xFFFF);
  gdt[num].granularity = ((limit >> 16) & 0x0F);

  /* Finally, set up the granularity and access flags */
  gdt[num].granularity |= (gran & 0xF0);
  gdt[num].access = access;
}

/* This will setup the special GDT pointer, set up the first 
 * 3 entries in our GDT, and then finally call gdt_flush() in our 
 * assembler file in order to tell the processor where the new GDT is 
 * and update the new segment registers */
void
gdt_install ()
{
  /* Setup the GDT pointer and limit */
  // XXX change this number if you have to add any more
  // gdt gates
  gdtp.limit = (sizeof (struct gdt_entry) * 6) - 1;
  gdtp.base = (unsigned int)(&gdt); // hacky

  // NULL
  gdt_set_gate (0, 0, 0, 0, 0);

  // kernel code and data
  gdt_set_gate (1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
  gdt_set_gate (2, 0, 0xFFFFFFFF, 0x92, 0xCF);

  // user code and data
  gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); 
  gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); 

  // tss garbage
  write_tss(5, 0x10, 0x0);

  gdt_flush();
  tss_flush();
}

/* idt stuff */

void
idt_set_gate (unsigned char num, unsigned long base, unsigned short sel,
	      unsigned char flags)
{
  /* The interrupt routine's base address */
  idt[num].base_lo = (base & 0xFFFF);
  idt[num].base_hi = (base >> 16) & 0xFFFF;

  /* The segment or 'selector' that this IDT entry will use
   *  is set here, along with any access flags */
  idt[num].sel = sel;
  idt[num].always0 = 0;
  idt[num].flags = flags;
}

void
idt_install ()
{
  /* Sets the special IDT pointer up */
  idtp.limit = (sizeof (struct idt_entry) * 256) - 1;
  idtp.base = (unsigned int)&idt; 

  /* Clear out the entire IDT, initializing it to zeros */
  memset ((void *) &idt, 0, sizeof (struct idt_entry) * 256);

  /* Points the processor's internal register to the new IDT */
  idt_load ();

}

